package com.longma.server.service.impl;


import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ReflectUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.longma.server.constant.enums.ResponseCode;
import com.longma.server.dao.RealtimeDataDAO;
import com.longma.server.dao.YuncangDAO;
import com.longma.server.entity.DailyData;
import com.longma.server.entity.RealtimeData;
import com.longma.server.entity.Yuncang;
import com.longma.server.exception.BusinessException;
import com.longma.server.pojo.dto.RealtimeDataAddDTO;
import com.longma.server.pojo.dto.YuncangAddDTO;
import com.longma.server.pojo.dto.YuncangUpdateDTO;
import com.longma.server.pojo.vo.home.PointVO;
import com.longma.server.service.*;
import com.longma.server.util.MathUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;


/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author HAIBO
 * @since 2021-04-28
 */
@Service
public class RealtimeDataServiceImpl implements IRealtimeDataService {

    @Autowired
    private RealtimeDataDAO realtimeDataDAO;

    @Autowired
    private YuncangDAO yuncangDAO;

    @Autowired
    private IAlarmRecordService alarmRecordService;

    @Autowired
    private IDailyDataService dailyDataService;

    @Autowired
    private IYuncangService yuncangService;

    @Autowired
    private IYuncangUserService yuncangUserService;

    // 功率曲线每个样本之间时间间隔 单位分钟
    private static final int TIME_INTERVAL = 1;

    // 实时功率指定时间范围  往前数个点
    private static final Integer POINT_COUNT = 30;

    /**
     * 上报数据
     */
    @Override
    public RealtimeData receiveDataFromYc(String message) {
        String[] gprsMessage = message.split(",");

        if (!gprsMessage[1].equals("11")) {
            return null;
        }
        if (gprsMessage[3].equals("44")) {
            return null;
        }

        Integer yuncangId = Integer.parseInt(gprsMessage[2]);
        LocalDateTime curTime = LocalDateTime.now();
        Double voltage = Double.parseDouble(gprsMessage[5]);
        Double current = Double.parseDouble(gprsMessage[6]);
        Double lon = Double.parseDouble(gprsMessage[8]);
        Double lat = Double.parseDouble(gprsMessage[9]);
        if (0 >= lon) {
            lon = null;
        }
        if (0 >= lat) {
            lat = null;
        }
        Double temp = MathUtil.round(2 * Math.random() + 25);
        Double humi = MathUtil.round(0.2 * Math.random() + 0.3);

        RealtimeData realtimeData = new RealtimeData().setBatteryChargeCurrent(current)
                .setBatteryVoltage(voltage)
                .setCreateTime(curTime)
                .setYuncangId(yuncangId)
                .setLongitude(lon)
                .setLatitude(lat)
                .setTemperature(temp)
                .setHumidity(humi);
        realtimeDataDAO.save(realtimeData);

        // 更新云仓经纬度  如果设备运行正常、会将设备状态标记为正常运行
        if (lon != null && lat != null) {
            YuncangUpdateDTO yuncangUpdateDTO = new YuncangUpdateDTO();
            yuncangUpdateDTO.setId(yuncangId);
            yuncangUpdateDTO.setLatitude(lat);
            yuncangUpdateDTO.setLongitude(lon);
            yuncangService.updateYuncang(yuncangUpdateDTO);
        }
        return realtimeData;
    }

    /**
     * 上报数据
     */
    @Override
    public RealtimeData receiveDataFromYc(RealtimeDataAddDTO addRequestDTO) {
        // 获取云仓
        Yuncang yuncang = yuncangDAO.getOne(Wrappers.<Yuncang>lambdaQuery().eq(Yuncang::getCollectorSn, addRequestDTO.getCollectorSn()));
        // 新注册云仓
        if (yuncang == null) {
            YuncangAddDTO yuncangAddDTO = new YuncangAddDTO();
            BeanUtil.copyProperties(addRequestDTO, yuncangAddDTO);
            yuncang = yuncangService.addYuncang(yuncangAddDTO);
        }

        // 插入实时数据
        RealtimeData realtimeData = new RealtimeData();
        BeanUtil.copyProperties(addRequestDTO, realtimeData);
        realtimeData.setYuncangId(yuncang.getId());
        realtimeDataDAO.save(realtimeData);

        // 插入日用电量、发电量数据
        if (realtimeData.getDailyConsumption() != null && realtimeData.getDailyGeneration() != null) {
            DailyData dailyData = new DailyData();
            dailyData.setYuncangId(yuncang.getId());
            dailyData.setDate(LocalDate.now());
            dailyData.setConsumption(realtimeData.getDailyConsumption());
            dailyData.setGeneration(realtimeData.getDailyGeneration());
            dailyDataService.addDailyData(dailyData);
        }

        // 生成告警记录 如果设备运行正常、会将设备状态标记为正常运行
        alarmRecordService.handleRealtimeData(realtimeData);

        // 更新云仓经纬度  如果设备运行正常、会将设备状态标记为正常运行
        if (realtimeData.getLongitude() != null && realtimeData.getLatitude() != null) {
            YuncangUpdateDTO yuncangUpdateDTO = new YuncangUpdateDTO();
            yuncangUpdateDTO.setId(yuncang.getId());
            yuncangUpdateDTO.setLatitude(realtimeData.getLatitude());
            yuncangUpdateDTO.setLongitude(realtimeData.getLongitude());
            yuncangService.updateYuncang(yuncangUpdateDTO);
        }
        return realtimeData;
    }

    /**
     * 获取指定时间内记录数据
     */
    @Override
    public Map<Integer, List<RealtimeData>> getRealtimeDataRecordMap(List<Integer> yuncangIds, LocalDateTime startTime, LocalDateTime endTime) {
        // 保存云仓数据
        Map<Integer, List<RealtimeData>> result = new HashMap<>();

        if (CollUtil.isEmpty(yuncangIds)) {
            return result;
        }

        // 查询起始时间
        List<RealtimeData> realtimeDatas = realtimeDataDAO.getRealtimeDataRecord(yuncangIds, startTime, endTime);
        for (RealtimeData realtimeData : realtimeDatas) {
            List<RealtimeData> list = result.get(realtimeData.getYuncangId());
            if (list == null) {
                list = new ArrayList<>();
            }
            list.add(realtimeData);
            result.put(realtimeData.getYuncangId(), list);
        }
        return result;
    }

    /*
     * 获取实时数据  取最新一条数据
     * 如果在配置超时时间内没有获取到数据，则不返回数据
     */
    @Override
    public Map<Integer, RealtimeData> getLatestRealtimeDataRecord(List<Integer> yuncangIds) {
        // 保存云仓数据
        Map<Integer, RealtimeData> result = new HashMap<>();

        if (CollUtil.isEmpty(yuncangIds)) {
            return result;
        }

        // 查询结束时间
        LocalDateTime startTime = LocalDateTime.now().minusMinutes(2);

        // 结束时间
        LocalDateTime endTime = startTime.plusMinutes(3);

        // 查询起始时间
        List<RealtimeData> realtimeDatas = realtimeDataDAO.getRealtimeDataRecord(yuncangIds, startTime, endTime);

        // 获取最新的数据
        for (RealtimeData realtimeData : realtimeDatas) {
            RealtimeData value = result.get(realtimeData.getYuncangId());
            if (value != null && realtimeData.getCreateTime().isBefore(value.getCreateTime())) {
                continue;
            }
            result.put(realtimeData.getYuncangId(), realtimeData);
        }

        return result;
    }

    /**
     * 获取实时功率 统计间隔为x分钟  x分钟内如果有多条数据取平均 如果某个时间点没有数据使用插值算法填充
     * 如果某个云仓一条数据也没有取到，则不返回数据
     * list中同一云仓数据是按照时间是从小到大排序  并且时间间隔相差x分钟
     */
    @Override
    public Map<Integer, List<PointVO>> getPowerGenerationCurve(List<Integer> yuncangIds, Integer pointNum) {
        return getCurve(yuncangIds, "pvPower", pointNum);
    }

    /**
     * 获取实时用电功率 统计间隔为x分钟  x分钟内如果有多条数据取平均 如果某个时间点没有数据使用插值算法填充
     * 如果某个云仓一条数据也没有取到，则不返回数据
     * list中同一云仓数据是按照时间是从小到大排序  并且时间间隔相差x分钟
     */
    @Override
    public Map<Integer, List<PointVO>> getPowerConsumptionCurve(List<Integer> yuncangIds, Integer pointNum) {
        return getCurve(yuncangIds, "usedPower", pointNum);
    }


    /**
     * 获取实时xxx曲线 统计间隔为x分钟  x分钟内如果有多条数据取平均 如果某个时间点没有数据使用插值算法填充
     * 如果某个云仓一条数据也没有取到，则不返回数据
     * list中同一云仓数据是按照时间是从小到大排序  并且时间间隔相差x分钟
     *
     * @param yuncangIds: 云仓id列表
     * @param columnName  ： 对应RealtimeData表列名
     * @param pointNum    : 点数
     */
    @Override
    public Map<Integer, List<PointVO>> getCurve(List<Integer> yuncangIds, String columnName, Integer pointNum) {
        // 保存云仓数据
        Map<Integer, List<PointVO>> result = new HashMap<>();

        if (CollUtil.isEmpty(yuncangIds)) {
            return result;
        }

        // 判断列名是否存在
        if (!ReflectUtil.hasField(RealtimeData.class, columnName)) {
            throw new BusinessException(ResponseCode.PARAM_NOT_VALID.setMessage("列不存在"));
        }


        // 查询结束时间  如 2021-05-18 12:30
        LocalDateTime curTime = LocalDateTime.now();
        LocalDateTime startTime = curTime.minusNanos(curTime.getNano()).minusSeconds(curTime.getSecond()).minusMinutes(pointNum * TIME_INTERVAL);

        // 结束时间
        LocalDateTime endTime = startTime.plusMinutes(pointNum * TIME_INTERVAL + 1);

        Map<Integer, List<RealtimeData>> realtimeDataMap = getRealtimeDataRecordMap(yuncangIds, startTime, endTime);

        // 生成时间序列
        List<LocalDateTime> timeSerial = new ArrayList<>();
        for (int i = 0; i <= pointNum; i++) {
            LocalDateTime time = startTime.plusMinutes(i * TIME_INTERVAL);
            timeSerial.add(time);
        }

        for (Map.Entry<Integer, List<RealtimeData>> entry : realtimeDataMap.entrySet()) {
            Integer yuncangId = entry.getKey();
            //  list中同一云仓数据是按照时间是从小到大排序
            List<RealtimeData> realtimeDatas = entry.getValue();

            // 对数据数据进行处理
            int index = 0;

            // 保存当前云仓数据
            List<PointVO> list = new ArrayList<>();

            Iterator<RealtimeData> iterator = realtimeDatas.iterator();
            while (iterator.hasNext() && index < pointNum) {
                RealtimeData realtimeData = iterator.next();
                // 第一个时间坐标
                LocalDateTime start = timeSerial.get(index);
                // 下一个时间坐标
                LocalDateTime next = timeSerial.get(index + 1);

                // 保存[start,end)时间范围内发电量
                double value = 0.0;

                // 保存该区间内采样点数
                int count = 0;

                //采样点介于两个时间之间  start <= curent < next
                while (realtimeData.getCreateTime().isBefore(next) && !realtimeData.getCreateTime().isBefore(start)) {
                    Double realtimeValue = null;
                    switch (columnName) {
                        case "pvPower":
                            realtimeValue = realtimeData.getPvPower();
                            break;
                        case "usedPower":
                            realtimeValue = realtimeData.getUsedPower();
                            break;
                        case "current":
                            realtimeValue = realtimeData.getCurrent();
                            break;
                        case "voltage":
                            realtimeValue = realtimeData.getVoltage();
                            break;
                        case "temperature":
                            realtimeValue = realtimeData.getTemperature();
                            break;
                        case "humidity":
                            realtimeValue = realtimeData.getHumidity();
                            break;
                        case "dailyConsumption":
                            realtimeValue = realtimeData.getDailyConsumption();
                            break;
                        case "dailyGeneration":
                            realtimeValue = realtimeData.getDailyGeneration();
                            break;
                        case "batteryVoltage":
                            realtimeValue = realtimeData.getBatteryVoltage();
                            break;
                        default:
                            break;
                    }
                    if (realtimeValue != null) {
                        count++;
                        value += realtimeValue;
                        if (iterator.hasNext()) {
                            realtimeData = iterator.next();
                        } else {
                            break;
                        }
                    }
                }

                // 计算均值
                if (count != 0) {
                    value = MathUtil.round(value / count);
                }

                list.add(new PointVO(start.toString(), value));

                index++;
            }
            result.put(yuncangId, list);
        }
        return result;
    }

    /**
     * 获取实时xxx列表曲线 统计间隔为x分钟  x分钟内如果有多条数据取平均 如果某个时间点没有数据使用插值算法填充
     * @param yuncangId: 云仓id
     * @param columnNames  ： 对应RealtimeData表列名 列表
     * @param pointNum    : 点数
     */
    @Override
    public Map<String, List<PointVO>> getCurve(Integer yuncangId, List<String> columnNames, Integer pointNum) {
        // 保存云仓数据
        Map<String, List<PointVO>> result = new HashMap<>();

        if (CollUtil.isEmpty(columnNames)) {
            return result;
        }

        // 判断列名是否存在
        for (String columnName : columnNames) {
            if (!ReflectUtil.hasField(RealtimeData.class, columnName)) {
                throw new BusinessException(ResponseCode.PARAM_NOT_VALID.setMessage("列不存在"));
            }
        }

        // 查询结束时间  如 2021-05-18 12:30
        LocalDateTime curTime = LocalDateTime.now();
        LocalDateTime startTime = curTime.minusNanos(curTime.getNano()).minusSeconds(curTime.getSecond()).minusMinutes(pointNum * TIME_INTERVAL);

        // 结束时间
        LocalDateTime endTime = startTime.plusMinutes(pointNum * TIME_INTERVAL + 1);

        // 查询起始时间
        List<RealtimeData> realtimeDatas = realtimeDataDAO.getRealtimeDataRecord(Arrays.asList(yuncangId), startTime, endTime);

        // 生成时间序列
        List<LocalDateTime> timeSerial = new ArrayList<>();
        for (int i = 0; i <= pointNum; i++) {
            LocalDateTime time = startTime.plusMinutes(i * TIME_INTERVAL);
            timeSerial.add(time);
        }

        for (String columnName : columnNames) {
            // 对数据数据进行处理
            int index = 0;

            // 保存当前云仓数据
            List<PointVO> list = new ArrayList<>();

            Iterator<RealtimeData> iterator = realtimeDatas.iterator();
            while (iterator.hasNext() && index < pointNum) {
                RealtimeData realtimeData = iterator.next();
                // 第一个时间坐标
                LocalDateTime start = timeSerial.get(index);
                // 下一个时间坐标
                LocalDateTime next = timeSerial.get(index + 1);

                // 保存[start,end)时间范围内发电量
                double value = 0.0;

                // 保存该区间内采样点数
                int count = 0;

                //采样点介于两个时间之间  start <= curent < next
                while (realtimeData.getCreateTime().isBefore(next) && !realtimeData.getCreateTime().isBefore(start)) {
                    Double realtimeValue = null;
                    switch (columnName) {
                        case "pvPower":
                            realtimeValue = realtimeData.getPvPower();
                            break;
                        case "usedPower":
                            realtimeValue = realtimeData.getUsedPower();
                            break;
                        case "current":
                            realtimeValue = realtimeData.getCurrent();
                            break;
                        case "voltage":
                            realtimeValue = realtimeData.getVoltage();
                            break;
                        case "temperature":
                            realtimeValue = realtimeData.getTemperature();
                            break;
                        case "humidity":
                            realtimeValue = realtimeData.getHumidity();
                            break;
                        case "dailyConsumption":
                            realtimeValue = realtimeData.getDailyConsumption();
                            break;
                        case "dailyGeneration":
                            realtimeValue = realtimeData.getDailyGeneration();
                            break;
                        case "batteryVoltage":
                            realtimeValue = realtimeData.getBatteryVoltage();
                            break;
                        default:
                            break;
                    }
                    if (realtimeValue != null) {
                        count++;
                        value += realtimeValue;
                        if (iterator.hasNext()) {
                            realtimeData = iterator.next();
                        } else {
                            break;
                        }
                    }
                }

                // 计算均值
                if (count != 0) {
                    value = MathUtil.round(value / count);
                }

                list.add(new PointVO(start.toString(), value));

                index++;
            }

            result.put(columnName,list);
        }

        return result;
    }

    /**
     * 获取登录用户所拥有云仓实时用电合计曲线
     */
    @Override
    public List<PointVO> getPowerConsumptionCurve() {
        return getPowerCurve(false);
    }

    /**
     * 获取登录用户所拥有云仓实时发电合计曲线
     */
    @Override
    public List<PointVO> getPowerGenerationCurve() {
        return getPowerCurve(true);
    }

    /**
     * 获取登录用户所拥有云仓实时合计曲线
     */
    private List<PointVO> getPowerCurve(boolean generation) {
        List<PointVO> result = new ArrayList<>();

        // 获取当前用户关联的云仓
        List<Integer> yuncangIds = yuncangUserService.getYuncangs().stream().map(Yuncang::getId).collect(Collectors.toList());
        if (CollUtil.isEmpty(yuncangIds)) {
            return result;
        }

        Map<Integer, List<PointVO>> map = null;

        if (generation) {
            map = getPowerGenerationCurve(yuncangIds, POINT_COUNT);
        } else {
            map = getPowerConsumptionCurve(yuncangIds, POINT_COUNT);
        }

        // 累加
        for (Map.Entry<Integer, List<PointVO>> item : map.entrySet()) {
            int count = 0;
            List<PointVO> pointVOs = item.getValue();
            for (PointVO pointVO : pointVOs) {
                if (result.size() <= count) {
                    result.add(pointVO);
                } else {
                    result.get(count).setValue(result.get(count).getValue() + pointVO.getValue());
                }
                count++;
            }
        }
        return result;
    }
}
